// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: xguo

#include <cmath>

#include <gflags/gflags.h>
#include <google/protobuf/util/json_util.h>
#include <nlohmann/json.hpp>

#include "coin2/base/config/app_config.h"
#include "coin2/base/config/args.h"
#include "coin2/base/hostname_util.h"
#include "coin2/base/log.h"
#include "coin2/exchange/base/log/strategy_logger.h"
#include "coin2/exchange/base/order/order_context_manager.h"
#include "coin2/exchange/factory/symbology/product.h"
#include "coin2/strategy/hooks/pnl_monitor.h"
#include "coin2/strategy/order_executor.pb.h"
#include "coin2/strategy/order_executor/mm_executor.h"
#include "coin2/strategy/strategy.h"

DEFINE_string(driver, "", "driver config");
DEFINE_string(owner, "", "Specify the owner of the account.");
DEFINE_string(mea, "", "Specify market.exchange.api_version.");
DEFINE_string(
    log_root,
    "/remote/iosg/strat-1/buckets/log.raw.coin/live/raw_og_log",
    "Specify log root.");
DEFINE_string(machine, "", "Specify machine name.");
DEFINE_string(trading_date, "", "Specify trading date.");
DEFINE_bool(find_og_instance_id, false, "find og instance id");
DEFINE_int64(og_instance_id, 0, "specify og instance id");

namespace {

using coin2::exchange::base::feed::FeedUpdate;
using coin2::exchange::base::order::OrderSpec;
using coin2::exchange::base::order::OrderUpdate;
using coin2::exchange::base::order_util::OrderLogReader;
using RawMessage = coin2::exchange::base::order::BaseParser::RawMessage;
using coin::proto::OrderExecutionProto;
using coin::proto::OrderGatewayLog;
using coin::proto::RawMessageProto;
using coin2::exchange::base::order::OrderSubTopicId;

using namespace coin2::app;
using namespace coin2::base::config;
};  // namespace

RawMessage FromProtoMsg(const OrderGatewayLog& log) {
  CHECK_EQ(log.type(), OrderGatewayLog::PRIVATE_EXCHANGE_MESSAGE) << log.type();
  RawMessageProto proto_msg;
  CHECK(proto_msg.ParseFromString(log.private_exchange_message()));
  RawMessage msg = RawMessage::FromProto(proto_msg);
  return msg;
}

int64_t DateStrToTimestamp(const std::string& date_str) {
  CHECK(coin2::exchange::base::symbology::IsDateStr(date_str)) << date_str;
  auto year = std::stoi(date_str.substr(0, 4));
  auto month = std::stoi(date_str.substr(4, 2));
  auto day = std::stoi(date_str.substr(6, 2));
  boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1));
  boost::posix_time::ptime date_time(boost::gregorian::date(year, month, day));
  return (date_time - epoch).total_seconds() * 1e9;
}

class StubStrategy : public coin2::strategy::IStrategy {
 public:
  StubStrategy() {}

  void Init(Driver* driver) override { driver_ = driver; }

  void onBookFeed(const FeedUpdate& upd) override {}
  void onAccountInfo(const OrderUpdate& upd) override {}
  void onAccountOrder(const OrderUpdate& upd) override {
    LOG(INFO) << "receive product_order info!" << upd.product_order_info().event().DebugString();
    LOG(INFO) << "OrderContext: " << upd.product_order_info().order_contexts().size();
  }
  void onOrderLog(const coin::proto::OrderGatewayLog& og_log) override {}

 private:
  Driver* driver_{nullptr};
  int64_t last_print_ts_{0};
};

int main(int argc, char* argv[]) {
  using coin::proto::AccountRequestProto;
  using coin2::exchange::base::order_util::OrderLogReader;
  using namespace coin2::exchange::base::market;

  google::InitGoogleLogging(argv[0]);
  gflags::ParseCommandLineFlags(&argc, &argv, true);

  CHECK_EQ(FLAGS_owner.empty(), false);
  CHECK_EQ(FLAGS_mea.empty(), false);
  CHECK_EQ(FLAGS_machine.empty(), false);
  CHECK_EQ(FLAGS_trading_date.empty(), false);

  auto path = std::experimental::filesystem::path(FLAGS_driver);
  DriverConfig app_config;
  app_config.MergeFrom(coin2::base::config::DriverConfigFromJson(path));
  LOG(INFO) << "app_config: " << app_config.DebugString();

  auto* order_config = app_config.mutable_order();
  for (auto& each_config : *(order_config->mutable_exchanges())) {
    auto log_root = each_config.second.order_logger_config().log_root() + "_replay";
    auto raw_log_root = each_config.second.order_logger_config().raw_log_root() + "_replay";
    each_config.second.mutable_order_logger_config()->set_log_root(log_root);
    each_config.second.mutable_order_logger_config()->set_raw_log_root(raw_log_root);
  }

  StubStrategy strategy;
  Driver driver(app_config, &strategy);

  auto subreq = MarketExchangeApi::FromString(FLAGS_mea);
  AccountRequestProto req_proto;
  req_proto.set_owner(FLAGS_owner);
  req_proto.set_market_type(MarketTypeToString(subreq.market));
  req_proto.set_exchange(ExchangeTypeToString(subreq.exchange));
  if (!subreq.api.empty()) {
    req_proto.set_api_version(subreq.api);
  }
  auto begin = DateStrToTimestamp(FLAGS_trading_date);
  auto end = begin + static_cast<int64_t>(24 * 3600 * 1e9);

  auto log_root = ExpandUser(FLAGS_log_root);
  OrderLogReader proto_reader(req_proto, log_root, FLAGS_machine, begin, end, 8192 * 800);
  int64_t ts;

  auto mea = MarketExchangeApi::FromString(FLAGS_mea);
  auto* os = driver.order()->GetUniqueSystem(mea);
  auto& processor = os->processor();
  coin::proto::OrderGatewayLog proto;
  int64_t num_msg = 0;

  int64_t prev_raw_msg_seq_id = 0;
  while (proto_reader.Read(&proto, &ts)) {
    num_msg++;
    auto og_id = (static_cast<uint64_t>(proto.raw_msg_id()) & 0x00FFFFFF00000000UL) >> 32;
    auto raw_msg_seq_id = (proto.raw_msg_id() & 0x00000000FFFFFFFFLL);
    if (proto.type() == OrderGatewayLog::ACCOUNT_REQUEST) {
      continue;
    }

    auto msg = FromProtoMsg(proto);
    if (msg.type >= 80 && msg.type < 90) {
      continue;
    }

    if (FLAGS_find_og_instance_id) {
      nlohmann::json json;
      if (raw_msg_seq_id == 1) {
        json["og_instance_id: "] = og_id;
        json["raw_msg_seq_id: "] = raw_msg_seq_id;
        json["timestamp"] = ts;
        LOG(INFO) << json.dump();
      }
      continue;
    }

    if (og_id != FLAGS_og_instance_id) {
      continue;
    }

    CHECK_EQ(prev_raw_msg_seq_id + 1, raw_msg_seq_id);
    prev_raw_msg_seq_id++;
    if (msg.type == 100) {
      continue;
    }
    processor.ProcessRawMessage(msg);
  }
  LOG(INFO) << "total num msg: " << num_msg;
  return 0;
}
